#ifndef UTILITY_H_
#define UTILITY_H_


#pragma warning (disable : 4244)

#include <fstream>
#include <memory>

#include "teem/nrrd.h"

#include "arithmetic.h"
#include "./utility/common_macro.h"
#include "./utility/color_printf.h"
#include "./utility/log.h"

template<typename T>
std::shared_ptr<T> shared_array(T* ptr)
{
    return std::shared_ptr<T>(ptr, std::default_delete<T[]>());
}


inline void readBinaryFile(const std::string& file_path, std::shared_ptr<char>& pBuffer, size_t& uSize)
{
    try
    {
        // open shader source file
        std::ifstream file(file_path, std::ios::in | std::ios::binary);
        if (!file.good())
        {
            std::string err = "fail to open source file: " + file_path + ".";
            throw std::exception(err.c_str());
        }
        // get the source file size
        std::ifstream::pos_type file_size = file.seekg(0, std::ios::end).tellg();
        if (-1 == file_size)
        {
            std::string err = "source file: " + file_path + " is empty.";
            throw std::exception(err.c_str());
        }
        file.seekg(0, std::ios::beg);   // reset the file pos at the begining

        uSize = file_size;
        pBuffer = shared_array(new char[uSize]);
        file.read(pBuffer.get(), uSize);

        file.close();
    }
    catch(std::exception& e)
    {
        Log::error(e.what());
        throw;
    }
}


inline void writeBinaryFile(const std::string& file_path, const char* pBuffer, const size_t uSize)
{
    try
    {
        // open shader source file
        std::ofstream file(file_path, std::ios::out | std::ios::binary);
        if (!file.good())
        {
            std::string err = "fail to open source file: " + file_path + ".";
            throw std::exception(err.c_str());
        }

        file.write(pBuffer, uSize);

        file.close();
    }
    catch(std::exception& e)
    {
        Log::error(e.what());
        throw;
    }
}


inline void readTensorByNrrd(
    const char* filename,
    std::shared_ptr<char>& pDiagonal,
    std::shared_ptr<char>& pOffDiagonal,
    size_t&uWidth,
    size_t& uHeight,
    size_t& uDepth)
{

    struct Tensor
    {
        float Mxx, Mxy, Mxz, Myx, Myy, Myz, Mzx, Mzy, Mzz;
    };

    struct Vertex
    {
        float x, y, z;
    };

    Nrrd *nin= nrrdNew();
    if (nrrdLoad(nin, filename, NULL))
    {
        char* err = biffGetDone(NRRD);
        size_t size = strlen(err) + 1;
        std::unique_ptr<char[]> pstr(new char[size]);
        memcpy(pstr.get(), err, size);
        free(err);
        throw std::exception(pstr.get());
    }

    uWidth = nin->axis[1].size;
    uHeight = nin->axis[2].size;
    uDepth = nin->axis[3].size;
    const size_t volumeSize = uWidth * uHeight * uDepth;

    size_t offset;
    Tensor* pTensor = (Tensor*)nin->data;
    Vertex* pdiag = new Vertex[volumeSize];
    Vertex* poffd = new Vertex[volumeSize];

    for (size_t z = 0; z < uDepth; z++)
    {
        offset = uWidth * uHeight * z;
        Tensor* pimgsrc  = pTensor + offset;
        Vertex* pimgdst0 = pdiag + offset;
        Vertex* pimgdst1 = poffd + offset;

        for(size_t y = 0; y < uHeight; y++)
        {
            offset = uWidth * y;
            Tensor* plinesrc = pimgsrc + offset;
            Vertex* plinedst0 = pimgdst0 + offset;
            Vertex* plinedst1 = pimgdst1 + offset;

            for(size_t x = 0; x < uWidth; x++)
            {
                plinedst0[x].x = plinesrc[x].Mxx;
                plinedst0[x].y = plinesrc[x].Myy;
                plinedst0[x].z = plinesrc[x].Mzz;

                plinedst1[x].x = plinesrc[x].Myz;
                plinedst1[x].y = plinesrc[x].Mxz;
                plinedst1[x].z = plinesrc[x].Mxy;
            }
        }
    }

    pDiagonal = shared_array((char*)pdiag);
    pOffDiagonal = shared_array((char*)poffd);
    nrrdNuke(nin);
}


inline void mat4_print(glm::mat4& mat)
{
    printf(Green,
        "[%0.2f, %0.2f, %0.2f, %0.2f]\n"
        "[%0.2f, %0.2f, %0.2f, %0.2f]\n"
        "[%0.2f, %0.2f, %0.2f, %0.2f]\n"
        "[%0.2f, %0.2f, %0.2f, %0.2f]\n", 
        mat[0][0], mat[1][0], mat[2][0], mat[3][0],
        mat[0][1], mat[1][1], mat[2][1], mat[3][1],
        mat[0][2], mat[1][2], mat[2][2], mat[3][2],
        mat[0][3], mat[1][3], mat[2][3], mat[3][3]);
}

#endif
